/*
 ***************************************************************************
 *
 * Author: Teunis van Beelen
 *
 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Teunis van Beelen
 *
 * teuniz@gmail.com
 *
 ***************************************************************************
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 ***************************************************************************
 *
 * This version of GPL is at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
 *
 ***************************************************************************
 */

/* last revision: Januari 31, 2014 */

/* For more info and how to use this library, visit: http://www.teuniz.net/RS-232/ */


#include "rs232.h"
#include "igslib_common_use.h"
#include "igslib_iutil.h"

#define MAX_SUPPORTED_PORTS 34

#ifdef __linux__   /* Linux */
static int Cport[MAX_SUPPORTED_PORTS], error;
int baudr;
bool en_9bit =0;

struct termios new_port_settings,
               old_port_settings[MAX_SUPPORTED_PORTS];

const char comports[MAX_SUPPORTED_PORTS][16]=
{
    "/dev/ttyS0","/dev/ttyS1","/dev/ttyS2","/dev/ttyS3","/dev/ttyS4","/dev/ttyS5",
    "/dev/ttyS6","/dev/ttyS7","/dev/ttyS8","/dev/ttyS9","/dev/ttyS10","/dev/ttyS11",
    "/dev/ttyS12","/dev/ttyS13","/dev/ttyS14","/dev/ttyS15","/dev/ttyUSB0",
    "/dev/ttyUSB1","/dev/ttyUSB2","/dev/ttyUSB3","/dev/ttyUSB4","/dev/ttyUSB5",
    "/dev/ttyAMA0","/dev/ttyAMA1","/dev/ttyACM0","/dev/ttyACM1",
    "/dev/rfcomm0","/dev/rfcomm1","/dev/ircomm0","/dev/ircomm1",
    "/dev/ttyXRUSB0","/dev/ttyXRUSB1","/dev/ttyXRUSB2","/dev/ttyXRUSB3",
};


void RS232_FlushBuffer(int com)
{
    tcflush(Cport[com], TCIOFLUSH);
}
void RS232_SetAttributes(enum parity_mode pm,enum data_bits db,enum stop_bits sb)
{
    memset(&new_port_settings, 0, sizeof(new_port_settings));  /* clear the new struct */
    new_port_settings.c_cflag = CLOCAL | CREAD;
    new_port_settings.c_cflag &= ~CRTSCTS;
    new_port_settings.c_iflag = IGNPAR;
    en_9bit =0;
    //printf("%s#1> cflag=%02x,iflag=%02x\n",__func__,new_port_settings.c_cflag,new_port_settings.c_iflag);

    if (db == db5) new_port_settings.c_cflag |= CS5;
    else if (db == db6) new_port_settings.c_cflag |= CS6;
    else if (db == db7) new_port_settings.c_cflag |= CS7;
    else if (db == db9)// 美洲版 9bit 
    {
        new_port_settings.c_cflag |= CS5; 
        en_9bit = 1;
    }

    else new_port_settings.c_cflag |= CS8;
    //printf("%s#2> db=%02x,cflag=%02x,iflag=%02x\n",__func__,db,new_port_settings.c_cflag,new_port_settings.c_iflag);

    if (pm == even)
    {
        new_port_settings.c_cflag |= PARENB;
    }
    else if (pm == odd)
    {
        new_port_settings.c_cflag |= PARENB;
        new_port_settings.c_cflag |= PARODD;
    }
    //printf("%s#3> pm=%02x,cflag=%02x,iflag=%02x\n",__func__,pm,new_port_settings.c_cflag,new_port_settings.c_iflag);

    if (sb == sb2) new_port_settings.c_cflag |= CSTOPB;
    //printf("%s#4> sb=%02x,cflag=%02x,iflag=%02x\n",__func__,sb,new_port_settings.c_cflag,new_port_settings.c_iflag);
}
int RS232_OpenComport(int comport_number, int baudrate)
{
    int status;

    if((comport_number>MAX_SUPPORTED_PORTS)||(comport_number<0))
    {
        printf("illegal comport number\n");
        return(1);
    }

    switch(baudrate)
    {
        case      50 : baudr = B50;
                       break;
        case      75 : baudr = B75;
                       break;
        case     110 : baudr = B110;
                       break;
        case     134 : baudr = B134;
                       break;
        case     150 : baudr = B150;
                       break;
        case     200 : baudr = B200;
                       break;
        case     300 : baudr = B300;
                       break;
        case     600 : baudr = B600;
                       break;
        case    1200 : baudr = B1200;
                       break;
        case    1800 : baudr = B1800;
                       break;
        case    2400 : baudr = B2400;
                       break;
        case    4800 : baudr = B4800;
                       break;
        case    9600 : baudr = B9600;
                       break;
        case   19200 : baudr = B19200;
                       break;
        case   38400 : baudr = B38400;
                       break;
        case   57600 : baudr = B57600;
                       break;
        case  115200 : baudr = B115200;
                       break;
        case  230400 : baudr = B230400;
                       break;
        case  460800 : baudr = B460800;
                       break;
        case  500000 : baudr = B500000;
                       break;
        case  576000 : baudr = B576000;
                       break;
        case  921600 : baudr = B921600;
                       break;
        case 1000000 : baudr = B1000000;
                       break;
        default      : printf("invalid baudrate\n");
                       return(1);
                       break;
    }

    Cport[comport_number] = open(comports[comport_number], O_RDWR | O_NOCTTY);
    if(Cport[comport_number]==-1)
    {
        std::stringstream ss;
        ss << __func__ << "/open> return=" << Cport[comport_number];
        igslib::util::Log(ss.str().c_str());
        return(1);
    }

    error = tcgetattr(Cport[comport_number], old_port_settings + comport_number);
    if(error==-1)
    {
        close(Cport[comport_number]);
        //perror("unable to read portsettings ");
        std::stringstream ss;
        ss << __func__ << "/tcgetattr> return=" << error;
        igslib::util::Log(ss.str().c_str());
        return(1);
    }

    new_port_settings.c_cflag |= baudr;
    //printf("%s> br=%02x,cflag=%02x,iflag=%02x\n",__func__,baudr,new_port_settings.c_cflag,new_port_settings.c_iflag);
    tcflush(Cport[comport_number],TCIOFLUSH);
    error = tcsetattr(Cport[comport_number], TCSANOW, &new_port_settings);
    if(error==-1)
    {
        close(Cport[comport_number]);
        //perror("unable to set portsettings ");
        std::stringstream ss;
        ss << __func__ << "/tcsetattr> return=" << error << ",";
        igslib::util::Log(ss.str().c_str());
        return(1);
    }
    return(0);
}


int RS232_PollComport(int comport_number, unsigned char *buf, int size)
{
    int n;
    
        n = read(Cport[comport_number], buf, size);
    // for(int k =0; k < n ; k++)
    // {
    //     printf("read buf %d : %x \n",k,buf[k]);
    // }
    
    return(n);
}


int RS232_SendByte(int comport_number, unsigned char byte)
{
    int n;

    n = write(Cport[comport_number], &byte, 1);
    if(n<0)  return(1);

    return(0);
}


int RS232_SendBuf(int comport_number, unsigned char *buf, int size)
{
    // for(int k =0; k < size ; k++)
    // {
    //     printf("send buf %d : %x \n",k,buf[k]);
    // }
    
        return(write(Cport[comport_number], buf, size));
   
}


void RS232_CloseComport(int comport_number)
{
    close(Cport[comport_number]);
}

/*
   Constant  Description
   TIOCM_LE  DSR (data set ready/line enable)
   TIOCM_DTR DTR (data terminal ready)
   TIOCM_RTS RTS (request to send)
   TIOCM_ST  Secondary TXD (transmit)
   TIOCM_SR  Secondary RXD (receive)
   TIOCM_CTS CTS (clear to send)
   TIOCM_CAR DCD (data carrier detect)
   TIOCM_CD  Synonym for TIOCM_CAR
   TIOCM_RNG RNG (ring)
   TIOCM_RI  Synonym for TIOCM_RNG
   TIOCM_DSR DSR (data set ready)

http://linux.die.net/man/4/tty_ioctl
*/

int RS232_IsDCDEnabled(int comport_number)
{
    int status;

    ioctl(Cport[comport_number], TIOCMGET, &status);

    if(status&TIOCM_CAR) return(1);
    else return(0);
}

int RS232_IsCTSEnabled(int comport_number)
{
    int status;

    ioctl(Cport[comport_number], TIOCMGET, &status);

    if(status&TIOCM_CTS) return(1);
    else return(0);
}

int RS232_IsDSREnabled(int comport_number)
{
    int status;

    ioctl(Cport[comport_number], TIOCMGET, &status);

    if(status&TIOCM_DSR) return(1);
    else return(0);
}

void RS232_enableDTR(int comport_number)
{
    int status;

    if(ioctl(Cport[comport_number], TIOCMGET, &status) == -1)
    {
        perror("unable to get portstatus");
    }

    status |= TIOCM_DTR;    /* turn on DTR */

    if(ioctl(Cport[comport_number], TIOCMSET, &status) == -1)
    {
        perror("unable to set portstatus");
    }
}

void RS232_disableDTR(int comport_number)
{
    int status;

    if(ioctl(Cport[comport_number], TIOCMGET, &status) == -1)
    {
        perror("unable to get portstatus");
    }

    status &= ~TIOCM_DTR;    /* turn off DTR */

    if(ioctl(Cport[comport_number], TIOCMSET, &status) == -1)
    {
        perror("unable to set portstatus");
    }
}

void RS232_enableRTS(int comport_number)
{
    int status;

    if(ioctl(Cport[comport_number], TIOCMGET, &status) == -1)
    {
        perror("unable to get portstatus");
    }

    status |= TIOCM_RTS;    /* turn on RTS */

    if(ioctl(Cport[comport_number], TIOCMSET, &status) == -1)
    {
        perror("unable to set portstatus");
    }
}

void RS232_disableRTS(int comport_number)
{
    int status;

    if(ioctl(Cport[comport_number], TIOCMGET, &status) == -1)
    {
        perror("unable to get portstatus");
    }

    status &= ~TIOCM_RTS;    /* turn off RTS */

    if(ioctl(Cport[comport_number], TIOCMSET, &status) == -1)
    {
        perror("unable to set portstatus");
    }
}


#else         /* windows */

#include <windows.h>
HANDLE Cport[MAX_SUPPORTED_PORTS];


const char comports[MAX_SUPPORTED_PORTS][10]={"\\\\.\\COM1",  "\\\\.\\COM2",  "\\\\.\\COM3",  "\\\\.\\COM4",
    "\\\\.\\COM5",  "\\\\.\\COM6",  "\\\\.\\COM7",  "\\\\.\\COM8",
    "\\\\.\\COM9",  "\\\\.\\COM10", "\\\\.\\COM11", "\\\\.\\COM12",
    "\\\\.\\COM13", "\\\\.\\COM14", "\\\\.\\COM15", "\\\\.\\COM16"};

char baudr[64];
static int last_baudrate = 115200;

void RS232_FlushBuffer(int com)
{
    PurgeComm(Cport[com], PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
}
void RS232_SetAttributes(parity_mode pm,data_bits db,stop_bits sb)
{
    char pmc = 'N';
    int dbn = 8;
    int sbn = 1;

    if (pm == even) pmc = 'E';
    else if (pm == odd) pmc = 'O';

    if (db == db5) dbn = 5;
    else if (db == db6) dbn = 6;
    else if (db == db7) dbn = 7;

    if (sb == sb2) sbn = 2;

    switch(last_baudrate)
    {
        case     110 :
        case     300 :
        case     600 :
        case    1200 :
        case    2400 :
        case    4800 :
        case    9600 :
        case   19200 :
        case   38400 :
        case   57600 :
        case  115200 :
        case  128000 :
        case  256000 :
        case  500000 :
        case 1000000 :
            sprintf(baudr,"baud=%d data=%d parity=%c stop=%d dtr=on rts=on",last_baudrate,dbn,pmc,sbn);
            break;
        default : printf("invalid baudrate\n");
                  break;
    }
}

int RS232_OpenComport(int comport_number, int baudrate)
{
    if((comport_number>MAX_SUPPORTED_PORTS)||(comport_number<0))
    {
        printf("illegal comport number\n");
        return(1);
    }

    last_baudrate = baudrate;

    Cport[comport_number] = CreateFileA(comports[comport_number],
            GENERIC_READ|GENERIC_WRITE,
            0,                          /* no share  */
            NULL,                       /* no security */
            OPEN_EXISTING,
            0,                          /* no threads */
            NULL);                      /* no templates */

    if(Cport[comport_number]==INVALID_HANDLE_VALUE)
    {
        printf("unable to open comport\n");
        return(1);
    }

    DCB port_settings;
    memset(&port_settings, 0, sizeof(port_settings));  /* clear the new struct  */
    port_settings.DCBlength = sizeof(port_settings);

    RS232_SetAttributes(none,db8,sb1);
    if(!BuildCommDCBA(baudr, &port_settings))
    {
        printf("unable to set comport dcb settings\n");
        CloseHandle(Cport[comport_number]);
        return(1);
    }

    if(!SetCommState(Cport[comport_number], &port_settings))
    {
        printf("unable to set comport cfg settings\n");
        CloseHandle(Cport[comport_number]);
        return(1);
    }

    COMMTIMEOUTS Cptimeouts;

    Cptimeouts.ReadIntervalTimeout         = MAXDWORD;
    Cptimeouts.ReadTotalTimeoutMultiplier  = 0;
    Cptimeouts.ReadTotalTimeoutConstant    = 0;
    Cptimeouts.WriteTotalTimeoutMultiplier = 0;
    Cptimeouts.WriteTotalTimeoutConstant   = 0;

    if(!SetCommTimeouts(Cport[comport_number], &Cptimeouts))
    {
        printf("unable to set comport time-out settings\n");
        CloseHandle(Cport[comport_number]);
        return(1);
    }

    return(0);
}


int RS232_PollComport(int comport_number, unsigned char *buf, int size)
{
    int n;

    /* added the void pointer cast, otherwise gcc will complain about */
    /* "warning: dereferencing type-punned pointer will break strict aliasing rules" */

    ReadFile(Cport[comport_number], buf, size, (LPDWORD)((void *)&n), NULL);

    return(n);
}


int RS232_SendByte(int comport_number, unsigned char byte)
{
    int n;

    WriteFile(Cport[comport_number], &byte, 1, (LPDWORD)((void *)&n), NULL);

    if(n<0)  return(1);

    return(0);
}


int RS232_SendBuf(int comport_number, unsigned char *buf, int size)
{
    int n;

    if(WriteFile(Cport[comport_number], buf, size, (LPDWORD)((void *)&n), NULL))
    {
        return(n);
    }

    return(-1);
}


void RS232_CloseComport(int comport_number)
{
    CloseHandle(Cport[comport_number]);
}

/*
http://msdn.microsoft.com/en-us/library/windows/desktop/aa363258%28v=vs.85%29.aspx
*/

int RS232_IsDCDEnabled(int comport_number)
{
    int status;

    GetCommModemStatus(Cport[comport_number], (LPDWORD)((void *)&status));

    if(status&MS_RLSD_ON) return(1);
    else return(0);
}


int RS232_IsCTSEnabled(int comport_number)
{
    int status;

    GetCommModemStatus(Cport[comport_number], (LPDWORD)((void *)&status));

    if(status&MS_CTS_ON) return(1);
    else return(0);
}


int RS232_IsDSREnabled(int comport_number)
{
    int status;

    GetCommModemStatus(Cport[comport_number], (LPDWORD)((void *)&status));

    if(status&MS_DSR_ON) return(1);
    else return(0);
}


void RS232_enableDTR(int comport_number)
{
    EscapeCommFunction(Cport[comport_number], SETDTR);
}


void RS232_disableDTR(int comport_number)
{
    EscapeCommFunction(Cport[comport_number], CLRDTR);
}


void RS232_enableRTS(int comport_number)
{
    EscapeCommFunction(Cport[comport_number], SETRTS);
}


void RS232_disableRTS(int comport_number)
{
    EscapeCommFunction(Cport[comport_number], CLRRTS);
}


#endif


void RS232_cputs(int comport_number, const char *text)  /* sends a string to serial port */
{
    while(*text != 0)   RS232_SendByte(comport_number, *(text++));
}


